//Copyright (c) 2013 Tilo Szepan, Immo Wache
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
//documentation files (the "Software"), to deal in the Software without restriction, including without limitation
//the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
//and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all copies or substantial portions 
//of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
//CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//DEALINGS IN THE SOFTWARE.

using System;
using System.Text;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Gtk;
using Gdk;
using System.IO;
using MonoMac;
using MonoMac.AppKit;
using MonoMac.Foundation;

namespace DuinoClient
{
	public class MainWindow: Gtk.Window
	{	
		FSReader reader;
		MacHelper mac;
		ProgressBar progressbar;
		ComboBoxEntry pathSelect;
		TreeView treeview;
		Statusbar statusbar;
		Button btn_refresh, btn_cancel;
		Pixbuf dirimg, fileimg;
		TreeStore tstore;

		string treepath;
		bool isNewFolder = false;
		bool isWaiting = false;
		bool isPaste = false;


		public MainWindow (): base (Gtk.WindowType.Toplevel)
		{
			reader = new FSReader();
			mac = new MacHelper();
			reader.Exceptions = HandleExceptions;
			reader.IsRemote = BeginWait;
			reader.Result = DefaultResultHandle;
			reader.SetClipboardFileList = mac.SetClipboard;
			reader.DoStep = ()=>{ progressbar.Adjustment.Value += 1; };
			dirimg = IconLoader.LoadIcon (this, "gtk-directory", IconSize.SmallToolbar);
			fileimg = IconLoader.LoadIcon (this, "gtk-file", IconSize.SmallToolbar);

			Build ();
			
			progressbar.Hide();
			mac.InitNSMenu(this);
			pathSelect.Active = 0;
		}
		void Build()
		{
			Title = "DuinoFinder";
			WindowPosition = Gtk.WindowPosition.Center;
			BorderWidth = (uint)6;
			var vbox1 = new Gtk.VBox ();
			vbox1.Spacing = 6;

			vbox1.PackStart(MainMenuBar(), false, true, 0);
			vbox1.PackStart(CmdLine(), false, false, 0);
			vbox1.Add(Treeview());
			vbox1.PackEnd(StatusBar(), false, true, 0);
			Add (vbox1);

			DeleteEvent += OnDeleteEvent;
			Child.ShowAll ();
			DefaultWidth = 800;
			DefaultHeight = 600;
			Show ();
		}
		MenuBar MainMenuBar()
		{
			MenuBar mb = new MenuBar ();
			Menu file_menu = new Menu ();
			MenuItem exit_item = new MenuItem("Exit");
			exit_item.Activated += delegate{ OnDeleteEvent(null, null); };
			file_menu.Append (exit_item);
			MenuItem file_item = new MenuItem("File");
			file_item.Submenu = file_menu;
			mb.Append (file_item);
			mb.Append(Menu_Edit());
			var hist = new MenuItem("History");
			var hist_menu = new Menu();
			var hist_edit = new MenuItem("Edit History");
			hist_menu.Append(hist_edit);
			hist.Submenu = hist_menu;
			mb.Append(hist);
			hist_edit.Activated += EditHistory;

			var jse = new MenuItem("Commands");
			var jse_open = new MenuItem("JSEditor");
			jse.Submenu = new Menu();
			(jse.Submenu as Menu).Append(jse_open);
			jse_open.Activated += openJSEditor;
			mb.Append(jse);

			return mb;
		}
		Widget CmdLine()
		{
			var btn_dir_up = new Button();
			var img = new Gtk.Image(IconLoader.LoadIcon (this, "gtk-go-up", Gtk.IconSize.Menu));
			btn_dir_up.Image = img;
			pathSelect = new ComboBoxEntry(reader.ReadHistory());
			btn_refresh = new Gtk.Button ();
			img = new Gtk.Image (IconLoader.LoadIcon (this, "gtk-refresh", Gtk.IconSize.Menu));
			btn_refresh.Image = img;
			btn_cancel = new Gtk.Button ();
			img = new Gtk.Image (IconLoader.LoadIcon (this, "gtk-cancel", Gtk.IconSize.Menu));
			btn_cancel.Image = img;
			btn_cancel.Sensitive = false;
			var btn_server = new Gtk.Button ();
			img = new Gtk.Image (IconLoader.LoadIcon (this, "gtk-connect", Gtk.IconSize.Menu));
			btn_server.Image = img;

			var hbox = new Gtk.HBox ();
			hbox.Spacing = 6;
			hbox.PackStart(btn_dir_up, false, false, 0);
			hbox.Add (pathSelect);
			hbox.PackEnd(btn_server, false, false, 0);
			hbox.PackEnd(btn_cancel, false, false, 0);
			hbox.PackEnd(btn_refresh, false, false, 0);

			btn_server.Clicked += delegate(object sender, EventArgs e) {
				var s = new ServerSelect(this);
				s.Show();
			};
			btn_dir_up.Clicked += delegate(object sender, EventArgs e) {
				var p = reader.GetParentDir(pathSelect.Entry.Text);
				if(p != null) pathSelect.Entry.Text = p;
			};
			btn_refresh.Clicked += delegate(object sender, EventArgs e) {
				Reload();
			};
			btn_cancel.Clicked += delegate(object sender, EventArgs e) {
				reader.Cancel();
				EndWait();
			};
			pathSelect.Changed += delegate(object sender, EventArgs e) {
				if(!pathSelect.Entry.HasFocus) Reload();
			};
			pathSelect.Entry.Activated += delegate(object sender, EventArgs e) {
				bool exists = false;
				foreach(var itm in (pathSelect.Model as ListStore)){
					var it = itm as object[];
					if(it[0].ToString() == pathSelect.ActiveText){
						exists = true;
						break;
					}
				}
				if(!exists){
					pathSelect.PrependText(pathSelect.ActiveText);
					reader.SaveHistory(GetHistory());
				}
				Reload();
			};

			return hbox;
		}
		Widget StatusBar()
		{
			statusbar = new Gtk.Statusbar ();
			statusbar.Spacing = 6;
			statusbar.HasResizeGrip = false;
			progressbar = new ProgressBar ();
			statusbar.PackEnd(progressbar, false, true, 0);
//			progressbar.Visible = false; //.Hide(); // has no effect until Show()
			return statusbar;
		}
		MenuItem Menu_Edit()
		{
			var cop = new MenuItem("Copy");
			cop.Activated += DoCopy;
			var pas = new MenuItem("Paste");
			pas.Activated += DoPaste;
			var del = new MenuItem("Delete");
			del.Activated += DoDelete;
			var fol = new MenuItem("New Folder");
			fol.Activated += DoNewFolder;
			var ren = new MenuItem("Rename");
			ren.Activated += BeginRename;
			var m = new Menu();
			m.Append(cop);
			m.Append(pas);
			m.Append(del);
			m.Append(new SeparatorMenuItem());
			m.Append(fol);
			m.Append(ren);

			var mi = new MenuItem("Edit");
			mi.Submenu = m;
			mi.Activated += delegate(object sender, EventArgs e) {
				var args = GetMenuArgs();
			
				cop.Sensitive = args.Copy;
				pas.Sensitive = args.FilesAvailable;
				del.Sensitive = args.Delete;
				ren.Sensitive = args.Rename;
			};
			return mi;
		}
		Widget Treeview()
		{
			this.treeview = new MTreeView();
			treeview.RulesHint = true;
			treeview.ButtonPressEvent += HandleTVButtonPressEvent;
			treeview.KeyPressEvent += HandleTVKeyPressEvent;
			var scrWin = new Gtk.ScrolledWindow ();
			scrWin.ShadowType = ShadowType.EtchedIn;
			scrWin.Add(treeview);
			treeview.Model = tstore = new TreeStore (typeof(Pixbuf), typeof (string), typeof (string), typeof (string));
			treeview.Selection.Mode = SelectionMode.Multiple;
			var pbren = new CellRendererPixbuf();
			pbren.Xalign = 0;
			var col = treeview.AppendColumn( "", pbren, "pixbuf", 0);
			col.Sizing = TreeViewColumnSizing.Autosize;
			var render = new CellRendererText ();
			render.Editable = true;
			render.Edited += DoRename;
			col = treeview.AppendColumn ("Name", render, "text", 1);
			col.Resizable = true;
			treeview.AppendColumn ("Date", new CellRendererText (), "text", 2);
			render = new CellRendererText ();
			render.Xalign = 1;
			render.Xpad = 20;
			col = treeview.AppendColumn ("Size", render, "text", 3);
			col.Resizable = true;
			treeview.AppendColumn(new TreeViewColumn());
			treeview.HeadersClickable = true;
			treeview.HeadersVisible = true;
			treeview.RowExpanded += RowExpandedHandle;

			return scrWin;
		}

		void openJSEditor (object sender, EventArgs e)
		{
			string initAddr = reader.IsRemotePath(pathSelect.Entry.Text) ? pathSelect.Entry.Text : null;
			var jse = new JSEditor(this, initAddr);
			jse.Show();
		}
		public void SetPath(string path)
		{
			pathSelect.Entry.Text = path;
		}
		string PathFromIter(TreeIter iter)
		{
			string path = tstore.GetValue(iter, 1).ToString();
			TreeIter par, chld = iter;
			while(tstore.IterParent(out par, chld)){
				path = tstore.GetValue(par, 1).ToString() + '/' + path;
				chld = par;
			}
			string bp = pathSelect.Entry.Text;
			if(bp.Last() != '/') bp += '/';
			if(IsDir(iter)) path += '/';
			return bp + path;
		}
		void Reload()
		{
			tstore.Clear();
			reader.GetDirInfo(pathSelect.Entry.Text);
		}
		void BeginWait(int cnt)
		{
			if(!isWaiting){
				isWaiting = true;
	//			treeview1.BinWindow.Cursor = new Cursor(CursorType.Watch);
				treeview.Sensitive = false;
				btn_cancel.Sensitive = true;
				statusbar.Push(1, "loading ...");
				progressbar.Adjustment.Value = 0;
				if(cnt > 0) progressbar.Adjustment.Upper = cnt;
				progressbar.Show();
			}
		}
		void EndWait()
		{
			if(isWaiting){
				isWaiting = false;
//				treeview1.BinWindow.Cursor = new Cursor(CursorType.BasedArrowUp);
				treeview.Sensitive = true;
				btn_cancel.Sensitive = false;
				statusbar.Pop(1);
				progressbar.Hide();
			}
		}

		void SyncResult(Action<object> act, object obj)
		{
			Gdk.Threads.AddIdle(1, ()=>{
				EndWait();
				act(obj);
				return false;
			});
		}

		void DefaultResultHandle(object obj)
		{
			if(isWaiting) SyncResult(DefaultResultHandle, obj);
			else{
				var res = obj as List<FileSystemItem>;
				if(res == null){
					if(isPaste){
						isPaste = false;
					}else{
						TreeIter iter;
						if(treepath != null && tstore.GetIterFromString(out iter, treepath)){
							var pb = IconLoader.LoadIcon (this, "gtk-cancel", IconSize.Menu);
							tstore.SetValue(iter, 0, pb);
							RemoveChildren(iter, -1);
						}
					}
					treepath = null;
				}else{
					TreeIter itr;
					if(treepath != null && tstore.GetIterFromString(out itr, treepath)){
						int cnt = tstore.IterNChildren(itr);
						foreach(var itm in res){
							TreeIter it = tstore.AppendValues (itr, itm.IsDir ? dirimg : fileimg, itm.Name, itm.Date, itm.Size);
							if(itm.IsDir) tstore.AppendValues(it, null, "");
						}
						treepath = null;
						if(!isPaste) RemoveChildren(itr, cnt);
					}else{
						foreach(var itm in res){
							TreeIter it = tstore.AppendValues (itm.IsDir ? dirimg : fileimg, itm.Name, itm.Date, itm.Size);
							if(itm.IsDir) tstore.AppendValues(it, null, "");
						}
					}
					isPaste = false;
				}
			}
		}
		void DeleteResultHandle(object obj)
		{
			reader.Result = DefaultResultHandle;
			EndWait();
			var res = obj as StringCollection;
			if(res != null){
				RemoveSelection();
			}
		}
		void RenameResultHandle(object obj)
		{
			reader.Result = DefaultResultHandle;
			EndWait();
			string res = obj as String;

			TreeIter itr;
			if(treepath != null && tstore.GetIterFromString(out itr, treepath)){
				if(isNewFolder){
					if(res == null) tstore.Remove(ref itr);
					else{
						string name = NameFromPath(res);
						tstore.SetValue(itr, 1, name);
					}
				}else{
					if(res != null)
						tstore.SetValue(itr, 1, res);
				}
			}
			treepath = null;
		}

		void RowExpandedHandle(object o, RowExpandedArgs args)
		{
			treepath = args.Path.ToString();
			string path = PathFromIter(args.Iter);
			reader.GetDirInfo(path);
		}
		void RemoveChildren(TreeIter iter, int cnt)
		{
			TreeIter chld;
			while(cnt != 0 && tstore.IterChildren(out chld, iter)){
				tstore.Remove(ref chld);
				cnt--;
			}
		}

		void HandleExceptions(Exception ex)
		{
			if(ex == null) return;
			Console.WriteLine (ex.StackTrace);
			Gdk.Threads.AddIdle(0, ()=>{
				var md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Close, false, ex.Message);
				md.Title = "an error has occurred";
				md.Run();
				md.Destroy();
				return false;
			});
		}
		public EditArgs GetMenuArgs()
		{
			int cnt = treeview.Selection.CountSelectedRows();
			return new EditArgs(cnt, mac.ClipboardHasFiles());
		}

		protected void OnDeleteEvent (object sender, DeleteEventArgs a)
		{
			Application.Quit ();
			if(a != null) a.RetVal = true;
			NSApplication.SharedApplication.Terminate(new NSObject());
		}
		public void EditHistory(object sender, EventArgs ea)
		{
			var hist = GetHistory();
			if(hist == null) return;
			var box = new VBox(true, 10);
			var chbtns = new CheckButton[hist.Length];
			int cnt = 0;
			foreach(var h in hist){
				chbtns[cnt] = new CheckButton(h);
				box.Add(chbtns[cnt++]);
			}
			var dlg = new Dialog("Edit History", this, DialogFlags.Modal, "Cancel", 1, "Clear All", 2, "Clear Selection", 3);
			box.ShowAll();
			dlg.VBox.Add(box);
			dlg.Response += delegate(object o, ResponseArgs args) {
				var mod = pathSelect.Model as ListStore;
				if((int)args.ResponseId == 2){
					mod.Clear();
					reader.SaveHistory(null);
				}else if((int)args.ResponseId == 3){
					TreeIter itr;
					for(int i = mod.IterNChildren() - 1; i >= 0; i--){
						if(chbtns[i].Active){
							hist[i] = null;
							if(mod.IterNthChild(out itr, i)) mod.Remove(ref itr);
						}
					}
					reader.SaveHistory(hist);
				}
			};
			dlg.Run();
			dlg.Destroy();
		}
		string[] GetHistory()
		{
			int n = pathSelect.Model.IterNChildren();
			if(n == 0) return null;
			var res = new string[n];
			n = 0;
			pathSelect.Model.Foreach((mod, path, iter)=>{
					res[n++] = mod.GetValue(iter, 0).ToString();
					return false;
				});
			return res;
		}
		void HandleTVKeyPressEvent (object o, KeyPressEventArgs e)
		{
			Console.WriteLine (	e.Event.State);
			if(e.Event.State == ModifierType.ControlMask ||
			   e.Event.State == (ModifierType.Mod1Mask | ModifierType.MetaMask)){
				if(e.Event.Key == Gdk.Key.c) DoCopy(o, e);
				else if(e.Event.Key == Gdk.Key.v) DoPaste(o, e);
			}
		}
		void HandleTVButtonPressEvent (object o, ButtonPressEventArgs e)
		{
			if(e.Event.Type == EventType.TwoButtonPress && treeview.Selection.CountSelectedRows() > 0){
				TreeIter sti; 
				var pth = treeview.Selection.GetSelectedRows()[0];
				if(tstore.GetIter(out sti, pth)){
					if(IsDir(sti)){
						var dir = PathFromIter(sti);
						pathSelect.Entry.Text = dir;
					}else Console.WriteLine ("is file");
				}
			}else OpenContextMenu();
		}
		void OpenContextMenu()
		{
			var args = GetMenuArgs();
			
			var cop = new MenuItem("Copy");
			cop.Activated += DoCopy;
			cop.Sensitive = args.Copy;

			var pas = new MenuItem("Paste");
			pas.Activated += DoPaste;
			pas.Sensitive = args.FilesAvailable;

			var del = new MenuItem("Delete");
			del.Activated += DoDelete;
			del.Sensitive = args.Delete;

			var fol = new MenuItem("New Folder");
			fol.Activated += DoNewFolder;

			var ren = new MenuItem("Rename");
			ren.Activated += BeginRename;
			ren.Sensitive = args.Rename;

			var his = new MenuItem("Edit History");
			his.Activated += EditHistory;

			var m = new Menu();
			m.Append(cop);
			m.Append(pas);
			m.Append(del);
			m.Append(new SeparatorMenuItem());
			m.Append(fol);
			m.Append(ren);
			m.Append(new SeparatorMenuItem());
			m.Append(his);
			m.ShowAll();
			m.Popup();
		}
		public void DoCopy(object sender, EventArgs ea)
		{
			reader.DoCopy(SelectionToPathlist());
		}
		public void DoPaste(object sender, EventArgs ea)
		{
			var fs = mac.ClipboardFileList();
			if(fs != null && fs.Count > 0){
				string subpath = null;
				var rows = treeview.Selection.GetSelectedRows();
				if(rows.Length == 1){
					TreeIter itr;
					if(tstore.GetIter(out itr, rows[0]) && IsDir(itr)){
						subpath = PathFromIter(itr);
						treepath = rows[0].ToString();
					}
				}
				if(subpath == null) subpath = pathSelect.Entry.Text;
				isPaste = true;
				reader.DoPaste(subpath, fs);
			}
		}
		public void DoDelete(object sender, EventArgs ea)
		{
			reader.Result = DeleteResultHandle;
			reader.DoDelete(SelectionToPathlist());
		}
		public void DoNewFolder(object sender, EventArgs ea)
		{
			var itr = tstore.AppendValues(dirimg, "NewFolder", DateTime.Now.ToString(), FileSystemItem.DirSize);
			treeview.SetCursor(tstore.GetPath(itr), treeview.Columns[1], true);
			isNewFolder = true;
		}
		public void BeginRename(object sender, EventArgs ea)
		{
			var pth = treeview.Selection.GetSelectedRows()[0];
			var col = treeview.Columns[1];
			treeview.SetCursor(pth, col, true);
		}
		void DoRename(object o, EditedArgs ea) {
			Console.WriteLine ("render.Edited " + ea.NewText);
			TreeIter itr;
			if(!tstore.GetIterFromString(out itr, ea.Path)) return;
			reader.Result = RenameResultHandle;
			treepath = ea.Path;
			if(isNewFolder) reader.InsertNewFolder(pathSelect.ActiveText +'/' + ea.NewText);
			else reader.Rename(PathFromIter(itr), ea.NewText);
		}

		void RemoveSelection()
		{
			var rows = treeview.Selection.GetSelectedRows();
			TreeIter itr;
			for(int i = rows.Length - 1; i >= 0; i--){
				if(tstore.GetIter(out itr, rows[i]))
					tstore.Remove(ref itr);
			}
		}
		string NameFromPath(string path)
		{
			int idx = path.TrimEnd('/').LastIndexOf('/');
			return path.Substring(idx + 1);
		}
		StringCollection SelectionToPathlist()
		{
			var rows = treeview.Selection.GetSelectedRows();
			rows = RemoveDescendants(rows);
			var res = new StringCollection();
			TreeIter itr;
			for(int i = 0; i < rows.Length; i++){
				if(tstore.GetIter(out itr, rows[i])){
					res.Add(PathFromIter(itr));// dir + 
			}}
			return res;
		}
		TreePath[] RemoveDescendants(TreePath[] items)
		{
			int nullcnt = 0;
			for(int i = 0; i < items.Length; i++)
			{
				for(int n = 0; n < items.Length; n++)
				{
					if(n == i) continue;
					if(items[n] != null && items[i].IsDescendant(items[n])){
						items[i] = null;
						nullcnt++;
						break;
					}
				}
			}
			if(nullcnt == 0) return items;
			var res = new TreePath[items.Length - nullcnt];
			int src = 0;
			for(int i = 0; i < res.Length; i++)
			{
				while(items[src] == null) src++;
				res[i] = items[src];
				src++;
			}
			return res;
		}
		bool IsDir(TreeIter it)
		{
			return FileSystemItem.DirSize == tstore.GetValue(it, 3).ToString();
		}
	}
	public class MTreeView : Gtk.TreeView
	{
		protected override bool OnButtonPressEvent (EventButton evnt)
		{
			return (evnt.Button == 3 || evnt.Type == EventType.TwoButtonPress) ? false : base.OnButtonPressEvent (evnt);
		}
		protected override bool OnKeyPressEvent (EventKey evnt)
		{
			if(evnt.State == ModifierType.ControlMask || evnt.State == (ModifierType.Mod1Mask | ModifierType.MetaMask))
				return false;
			return base.OnKeyPressEvent (evnt);
		}
	}
	public class EditArgs
	{
		int cnt;
		public bool FilesAvailable{get; private set;}
		public bool Copy{get{ return cnt > 0; }}
		public bool Delete{get{ return cnt > 0; }}
		public bool Rename{get{ return cnt == 1; }}
		public EditArgs(int selectionCnt, bool filesAvailable)
		{
			cnt = selectionCnt;
			FilesAvailable = filesAvailable;
		}
	}
}
